﻿namespace Business
open Business.Repositories
open Data.ViewModels
open Data.Settings
open System
open System.Linq
open System.Diagnostics
[<AutoOpen>]
module Domains =
  type Domain(uow:IUow) =
     member x.Uow = uow
  
  type ISeachContext =
     abstract SearchInUserScope : int -> Int64 array -> Async< IQueryable<Data.Url>>

  type IBookmarkContext =
      abstract sayHi: int
      abstract CreateAsync: Int64 -> Int64 array -> Async<Data.Bookmark array>
      abstract Query : (IQueryable<Data.Bookmark> -> IQueryable<'a>) -> IQueryable<'a>
      abstract SetBookmarkToUser : int -> Int64 array -> Async<Data.UserBookmark array>

  type IUserContext = 
       abstract GetUser: string -> Data.UserProfile
       abstract CreateUser: Data.UserProfile -> unit
  type IUrlContext = 
     abstract FindBy:string -> Data.Url
     abstract FindBy:Int64 -> Data.Url
     abstract Create: Data.Url -> Data.Url
     abstract Create: Uri * bool-> Data.Url
     abstract GetIDForUrlAsync: string -> Async<Int64>
  type ITagContext =
     abstract FindBy:string -> Data.Tag
     abstract FindBy:Int64 -> Data.Tag
     abstract FindAll:string array -> IQueryable<Data.Tag>
     abstract Create: Data.Tag -> Data.Tag
     abstract Create: Data.Tag array -> Data.Tag array
     abstract Create: string -> Data.Tag
     abstract Create: string array -> Data.Tag array
     abstract GetIdForTagsAsync: string array -> Async<Int64 array>
     abstract GetTagsForUrlAsync : Int64 -> Async<(Int64 * Data.Tag) array>
    
  type UrlContext(uow:IUow) =
   inherit Domain(uow)
    interface IUrlContext with
      member x.FindBy url =  x.Uow.Url.FilterOne ( <@ fun s -> s.URI = url @> )
      member x.FindBy id = x.Uow.Url.FilterOne ( <@ fun s -> s.Id = id @> )
      member x.Create url = x.Uow.Url.Create url
      member x.Create (uri,isWWW) = x.Uow.Url.Create (Data.Url(URI = uri.AbsoluteUri, HostUrl = uri.Host, isWWW = isWWW, isFile = uri.IsFile, LastModified = DateTime.UtcNow))
      member x.GetIDForUrlAsync uriString = async {
                     let u = (x :> IUrlContext).FindBy ((Utils.url uriString).Value |> fst).AbsoluteUri
                     if u = null then
                        let u =  (Utils.url uriString).Value |>  ( x :> IUrlContext).Create
                        let commit = x.Uow.Commit() 
                        return u.Id
                     else 
                        return u.Id }

                     
  type TagContext (uow:IUow) =
    inherit Domain(uow)
     interface ITagContext with
       member x.FindBy (tag:string) = x.Uow.Tags.FilterOne (<@fun s -> s.TAG = tag@>)
       member x.FindBy Id = x.Uow.Tags.FilterOne (<@fun s -> s.Id = Id@>)
       member x.FindAll tags = x.Uow.Tags.Filter <@fun s -> tags.Contains(s.TAG)  @>
       member x.Create tag = x.Uow.Tags.Create tag
       member x.Create tag = x.Uow.Tags.Create (Data.Tag(TAG = tag))
       member x.Create tags = tags |> Array.map ( fun (t:Data.Tag) -> (x:>ITagContext).Create t)
       member x.Create tags = tags |> Array.map ( fun (t:string) -> (x:>ITagContext).Create t)
       member x.GetIdForTagsAsync tags = 
          async {
          let normTags = [|for tag in tags do yield! tag.Split(Data.Settings.Settings.createSeparators, StringSplitOptions.RemoveEmptyEntries) |] |> Array.map (fun s -> s.ToLower())
          let existedTags = (normTags |> (x :> ITagContext).FindAll).ToArray()
          let existedTagsString = existedTags |> Array.map (fun s -> s.TAG)
          let tagsToCreate = normTags |> Array.filter (fun s ->  existedTagsString.Any( fun q -> q = s) |> not )
          let existIds =  (existedTags |> Array.map (fun s -> s.Id))
          if tagsToCreate.Length = 0 then
             return existIds
          else 
             let createdTags = tagsToCreate |> Array.map (x:> ITagContext).Create
             x.Uow.Commit() |> ignore
             return createdTags |> Array.map (fun s -> s.Id) |> Array.append existIds
          }
       member x.GetTagsForUrlAsync uId = async { 
                              let iqtags = (x.Uow.Bookmarks.Filter <@ fun b -> b.UrlId = uId @>)
                              let fivetags = iqtags.Take(5).ToArray()
                              let f = fivetags |> Array.map (fun (b:Data.Bookmark) -> (uId,b.Tag)) //.Select(fun (b:Data.Bookmark) ->  (uId,b.Tag))
                              let r = f.ToArray()
                              return r
                         }

  type BookmarkContext(uow:IUow) = 
    inherit Domain(uow)
     interface IBookmarkContext with
       member x.sayHi = 5
       member x.CreateAsync uriId tagIds = async {
           let containBkmrks = (x.Uow.Bookmarks.Filter (<@ fun b -> b.UrlId = uriId && tagIds.Contains(b.TagId) @> )).ToArray()
           let containTagsId = containBkmrks |> Array.map  (fun b -> b.TagId)
           let tagIdsNew = tagIds |> Array.filter ( fun t -> t |> containTagsId.Contains |> not )
           let bkmrksCreated = tagIdsNew |> Array.map ( fun t -> x.Uow.Bookmarks.Create(Data.Bookmark(TagId = t, UrlId = uriId)) )
           if bkmrksCreated.Length > 0 then
              x.Uow.Commit() |> ignore
           else
              ()
           return (bkmrksCreated |> Array.append containBkmrks) }
       member x.Query source = 
                    query { for x in (source (x.Uow.Bookmarks.GetAll())) do 
                                     select x }
       member x.SetBookmarkToUser userId bookmarksId =
          async {
            let containUserBookmarks = (x.Uow.UsersBookmarks.Filter ( <@ fun u -> u.UserId = userId && bookmarksId.Contains(u.BookmarkId) @>  )).ToArray()
            let containBookmarksId =  containUserBookmarks |> Array.map ( fun b -> b.BookmarkId)
            let bookmarksIdNew = bookmarksId |> Array.filter (fun b -> b |> containBookmarksId.Contains |> not)
            let results = bookmarksIdNew |> Array.map (fun b -> new Data.UserBookmark(UserId = userId,BookmarkId = b) |> x.Uow.UsersBookmarks.Create ) 
            if results.Length > 0 then 
               x.Uow.Commit() |> ignore
            else 
               ()
            return containUserBookmarks |>  Array.append results;
          }

  type UserContext(uow:IUow) =
    inherit Domain(uow)
    interface IUserContext with
     member this.GetUser name = this.Uow.Users.FilterOne (<@fun u -> u.UserName.ToLower() = name.ToLower()@>)
     member this.CreateUser user = this.Uow.Users.Create user |> ignore

  type SeachContext(uow : IUow) =
     inherit Domain(uow)
     interface ISeachContext with 
        member x.SearchInUserScope userId tagsId = 
           async {
              let u = x.Uow.UsersBookmarks.Filter (<@ fun ub -> ub.UserId = userId @> )
              return query {
                 for ub in u do
                 where (tagsId.Contains(ub.Bookmark.TagId))
                 groupBy ub.Bookmark.Url into g
                 where (g.Count() = tagsId.Length) 
                 sortByDescending g.Key.position 
                 select g.Key
              }
           }

module BusinessDomain =
  type IBusinessContext =
    abstract BookmarksContext : Domains.IBookmarkContext
    abstract UserContext: Domains.IUserContext
    abstract TagContext: Domains.ITagContext
    abstract UrlContext: Domains.IUrlContext
    abstract SeachContext: Domains.ISeachContext
    abstract AddBookmarks: b:Data.ViewModels.Bookmark * ?UserName:string -> Async<Data.Bookmark array>



  type BusinessContext(bookmarks:Domains.IBookmarkContext, users:Domains.IUserContext, tags: Domains.ITagContext, urls: Domains.IUrlContext, search: ISeachContext) =
    interface IBusinessContext with
      member x.BookmarksContext = bookmarks
      member x.UserContext = users
      member x.TagContext = tags
      member x.UrlContext = urls
      member x.SeachContext = search
      member x.AddBookmarks (b , ?user) = 
         async {
           let! urlId = (x :> IBusinessContext).UrlContext.GetIDForUrlAsync b.url
           let q = match user with
           | Some u -> (x :> IBusinessContext).UserContext.GetUser u |> (fun us ->  us.UrlSettings.Add <| new Data.UserUrlSettings( UrlId = urlId, UserId = us.Id, Title = b.title ))
           | None -> ()
           let! tagsID = (x :> IBusinessContext).TagContext.GetIdForTagsAsync b.tags
           return! (x :> IBusinessContext).BookmarksContext.CreateAsync urlId tagsID
         }